/***************************************************************************
 *   Copyright (C) 2008 by JBox systems
 *   gabi@shaargiora.co.il
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 ***************************************************************************/

#include <QDebug>

#include <boost/filesystem.hpp>
#include <iostream>

#include "download.h"
#include "memory.h"

Downloader::Downloader(std::string source, std::string destination, QObject *parent):
	QObject(parent),
	file(NULL),
	url(new QUrl(source.c_str())),
	httpRequestAborted(false),
	destination(destination) {

	scheme_lookup["http"] = HTTP;
	scheme_lookup["ftp"] = FTP;

	downloadFile();
}

void Downloader::downloadFile() {
	switch(scheme_lookup[url->scheme().toStdString()]) {
	case HTTP:
		http = new QHttp(this);
		connect(http, SIGNAL(requestFinished(int, bool)),this, SLOT(httpRequestFinished(int, bool)));
		connect(http, SIGNAL(dataReadProgress(int, int)),this, SIGNAL(dataReadProgress(int, int)));
		connect(http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),this, SLOT(readResponseHeader(const QHttpResponseHeader &)));
		connect(http, SIGNAL(done(bool)), this, SIGNAL(downloadFinished(bool)));
		connect(http, SIGNAL(readyRead(const QHttpResponseHeader&)), this, SLOT(writeToLocalFile(const QHttpResponseHeader&)));
// 		httpRequest();
		downloadHttpFile();
		break;
	case FTP:
		ftp = new QFtp(this);
		connect(ftp, SIGNAL(dataTransferProgress(qint64, qint64)), this, SLOT(updateDataTransferProgress(qint64, qint64)));
		connect(ftp, SIGNAL(commandFinished(int, bool)), this, SLOT(ftpCommandFinished(int, bool)));
		connect(ftp, SIGNAL(done(bool)), this, SIGNAL(downloadFinished(bool)));
		ftp->connectToHost(url->host());
		ftp->login();
		break;
	default: //unsupported scheme:
		break;
	}
}

void Downloader::downloadFtpFile()  {

	validatePath();

	QFileInfo fileInfo(url->path());
	QString fileName = (boost::filesystem::path(destination) /  fileInfo.fileName().toStdString()).string().c_str();

	if (QFile::exists(fileName))
		QFile::remove(fileName);
	file = new QFile(fileName);

	if (!file->open(QIODevice::WriteOnly)) {
		DELETE(file)
		return;
	}

	ftp->get(url->path(), file);
	ftp->close();
}

void Downloader::updateDataTransferProgress(qint64 readBytes, qint64 totalBytes) {
	emit dataReadProgress((int) readBytes, (int) totalBytes);
}

void Downloader::ftpCommandFinished(int /* commandId */, bool error) {
	if (error) {
		file->close();
		qDebug("error connecting to ftp");
		return;
	}

	if (ftp->currentCommand() == QFtp::Login) {
		qDebug("connected to ftp");
		downloadFtpFile();
	} else if (ftp->currentCommand() == QFtp::Close) {
		qDebug("file downloaded");
		file->close();
	}
}

bool Downloader::openDownloadFile() {
	validatePath();

	QFileInfo fileInfo(url->path());
	QString fileName = (boost::filesystem::path(destination) /  fileInfo.fileName().toStdString()).string().c_str();

	// Remove existing file
	if (QFile::exists(fileName))
		QFile::remove(fileName);

	file = new QFile(fileName);

	// Open local file for writing.
	if (!file->open(QIODevice::Truncate|QIODevice::WriteOnly)) {
		DELETE(file)
		return false;
	}

	return true;
}

void Downloader::downloadHttpFile() {

	// Set connection mode
	QHttp::ConnectionMode mode = url->scheme().toLower() == "https" ? QHttp::ConnectionModeHttps : QHttp::ConnectionModeHttp;
	http->setHost(url->host(), mode, url->port() == -1 ? 0 : url->port());

	// Set authentication info
	if (!url->userName().isEmpty())
		http->setUser(url->userName(), url->password());

	// Prepare url (escape)
	QByteArray download_url = QUrl::toPercentEncoding(url->path(), "!$&'()*+,;=:@/");
	if (download_url.isEmpty())
		download_url = "/";

	httpGetId = http->get(download_url);
}

void Downloader::cancelDownload() {
	httpRequestAborted = true;
	if (http)
		http->abort();
	else
		ftp->abort();
}

void Downloader::httpRequestFinished(int requestId, bool /* error */) {

	// Not the get request. ignore.
	if (requestId != httpGetId)
		return;

		// TODO experiment with this
// 	if (error)
// 		httpRequestAborted = true;

	// Request aborted
	if (httpRequestAborted) {
		if (file) {
			file->close();
			file->remove();
			DELETE(file);
		}
		return;
	}

	// Finish writing and close local file.
	if (file) {
		file->write(http->readAll());
		file->close();
	}
}

void Downloader::readResponseHeader(const QHttpResponseHeader &responseHeader) {
// 	qDebug() << "response: " << responseHeader.statusCode();
	switch (responseHeader.statusCode()) {

	case 301:                   // Moved Permanently
	case 302:                   // Found
	case 303:                   // See Other
	case 307:                   // Temporary Redirect
		if (responseHeader.value("Location") != "" )
			url = new QUrl(responseHeader.value("Location"));
		break;
	case 200:    // Ok
		if (!openDownloadFile())
			cancelDownload();
		break;
	default:
		httpRequestAborted = true;
		http->abort();
	}
}

void Downloader::writeToLocalFile(const QHttpResponseHeader& /* resp */) {

	// No open file yet. write to buffer.
	if (!file) {
// 		qDebug() << "Writing to buffer";
		buffer.append(http->readAll());
		return;
	}

	// Empty buffer into file.
	if (buffer.length() != 0) {
// 		qDebug() << "Clearing buffer";
		file->write(buffer);
		buffer.clear();
	}

	file->write(http->readAll());
}

void Downloader::validatePath() {
	if (!boost::filesystem::exists(boost::filesystem::path(destination)))
		boost::filesystem::create_directory(boost::filesystem::path(destination));
}
